﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Luminal;

namespace Luminoise
{
    public sealed class NoiseProgram : IDisposable
    {
        const string KernelCode =
@"__kernel void cl_main(__global int *perm, __global Single3 *input, __global float *output)
{
    int i = get_global_id(0);
    output[i] = cl_module(perm, input[i]);
}
";
        Program program;
        Dictionary<int, MemoryObject> inputBufferCache = new Dictionary<int, MemoryObject>();
        Dictionary<int, MemoryObject> outputBufferCache = new Dictionary<int, MemoryObject>();

        public Module Module { get; set; }

        public int Seed { get; set; }

        public string GeneratedSource { get { return Module.GetCode() + KernelCode.Replace("cl_module", Module.FunctionName); } }

        public NoiseProgram(Module module)
            : this(module, new Random().Next())
        {

        }

        public NoiseProgram(Module module, int seed)
        {
            Module = module;
            Seed = seed;
        }

        public void Compile()
        {
            if (program != null)
                program.Dispose();

            program = Program.FromSource(NoiseCore.Context, NoiseCore.IncludeCode, GeneratedSource);

            string[] buildLogs;
            if (!program.Build(NoiseCore.Context.Devices, null, out buildLogs))
            {
                throw new Exception(buildLogs[0]);
            }
        }

        public float[] GetValues(Single3[] input)
        {
            float[] output = new float[input.Length];

            MemoryObject permBuffer = NoiseCore.GetPermutationBuffer(Seed);
            if (!inputBufferCache.ContainsKey(input.Length))
                inputBufferCache.Add(input.Length, MemoryObject.CreateBuffer<Single3>(NoiseCore.Context, MemoryAccessFlags.ReadOnly, input.Length));
            if (!outputBufferCache.ContainsKey(input.Length))
                outputBufferCache.Add(input.Length, MemoryObject.CreateBuffer<float>(NoiseCore.Context, MemoryAccessFlags.WriteOnly, input.Length));
            MemoryObject inputBuffer = inputBufferCache[input.Length];
            MemoryObject outputBuffer = outputBufferCache[input.Length];

            program.Kernels["cl_main"].SetArgument(0, permBuffer);
            program.Kernels["cl_main"].SetArgument(1, inputBuffer);
            program.Kernels["cl_main"].SetArgument(2, outputBuffer);

            Event writeEvent = NoiseCore.CommandQueue.EnqueueWriteBuffer<Single3>(inputBuffer, true, input);
            Event kernelEvent = NoiseCore.CommandQueue.EnqueueNDRangeKernel(program.Kernels["cl_main"], new int[] { input.Length }, null, writeEvent);
            Event readEvent = NoiseCore.CommandQueue.EnqueueReadBuffer<float>(outputBuffer, true, output, kernelEvent);

            writeEvent.Dispose();
            kernelEvent.Dispose();
            readEvent.Dispose();

            return output;
        }

        public void Dispose()
        {
            foreach (MemoryObject obj in inputBufferCache.Values)
                obj.Dispose();
            foreach (MemoryObject obj in outputBufferCache.Values)
                obj.Dispose();
            if (program != null)
                program.Dispose();
        }
    }
}
